package org.smile.excel.jdbc;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.Stack;

import org.smile.commons.SmileRunException;
import org.smile.json.parser.JSONParseException;

public class ExcelSqlParser {

	private int character;
	private boolean eof;
	private int index;
	private int line;
	private char previous;
	private final Reader reader;
	private boolean usePrevious;
	Stack<Character> stack=new Stack<Character>();
	static String start="select * from (";
	static String end=") a where 1=0";
	
	
	public static Statement parse(String sql) throws IOException{
		boolean onlyMetaData=false;
		if(sql.startsWith(start)&&sql.endsWith(end)){
			sql=sql.substring(start.length(),sql.length()-end.length());
			onlyMetaData=true;
		}
		StringReader reader=new StringReader(sql);
		ExcelSqlParser parser=new ExcelSqlParser(reader);
		String statementName=parser.nextString(' ');
		Statement statement=null;
		if("SELECT".equalsIgnoreCase(statementName)){
			statement=new Select();
			parser.parseColumns((Select)statement,reader);
			if(onlyMetaData){
				WhereExpress where=new WhereExpress();
				where.setConsts(false);
				((Select)statement).setWhere(where);
			}
		}
		return statement;
	}

	private void parseTableName(Select select, Reader reader) throws IOException{
		String table=nextExpress(reader);
		ExpressColumn tableexp=new ExpressColumn(table);
		select.tableName=tableexp;
		String where=nextExpress(reader);
		if(where!=null){
			WhereExpress we=new WhereExpress();
			we.setExpression(nextString((char)-1));
			select.setWhere(we);
		}
	}
	
	private void parseColumns(Select select, Reader reader) throws IOException {
		String exp=this.nextExpress(reader);
		if("*".equals(exp)){
			ExpressColumn column=new ExpressColumn("*");
			select.addColumn(column);
			select.setIsAll();
			String from=nextExpress(reader);
			if("FROM".equalsIgnoreCase(from)){
				parseTableName(select, reader);
			}
			return ;
		}else if("FROM".equalsIgnoreCase(exp)){
			parseTableName(select, reader);
			return ;
		}
		this.back();
		char n=nextClean();
		ExpressColumn ec;
		switch(n){
			case ',':
				ec=new ExpressColumn(exp);
				select.addColumn(ec);
				parseColumns(select,reader);
				break;
			case '\n':
			case '\r':
				break;
			default:
				back();
				String next=nextExpress(reader);
				if("AS".equalsIgnoreCase(next)){
					String axlis=nextExpress(reader);
					ec=new ExpressColumn(exp,axlis);
					select.addColumn(ec);
					parseColumns(select,reader);
				}else if("FROM".equalsIgnoreCase(next)){
					ec=new ExpressColumn(exp);
					select.addColumn(ec);
					parseTableName(select, reader);
					return ;
				}
		}
	}
	
	private String nextExpress(Reader reader) throws IOException{
		StringBuffer sb=new StringBuffer();
		char n=this.nextClean();
		if(end()){
			return null;
		}
		this.back();
		for(;;){
			char c=this.next();
			if(end()){
				break;
			}
			switch(c){
				case '\n':
				case '\r':
					return sb.toString();
				case ' ':
				case ',':
					if(stack.isEmpty()){
						return sb.toString();
					}
					break;
				case '(':
					this.stack.push(c);
					sb.append(c);
					break;
				case ')':
					this.stack.pop();
					sb.append(c);
					break;
				default:
					sb.append(c);
			}
		}
		return sb.toString();
	}

	public ExcelSqlParser(Reader reader) {
		this.reader = reader.markSupported() ? reader : new BufferedReader(
				reader);
		this.eof = false;
		this.usePrevious = false;
		this.previous = 0;
		this.index = 0;
		this.character = 1;
		this.line = 1;
	}

	public ExcelSqlParser(InputStream inputStream){
		this(new InputStreamReader(inputStream));
	}

	public ExcelSqlParser(String s) {
		this(new StringReader(s));
	}

	public void back() {
		if (this.usePrevious || this.index <= 0) {
			throw new SmileRunException("Stepping back two steps is not supported");
		}
		this.index -= 1;
		this.character -= 1;
		this.usePrevious = true;
		this.eof = false;
	}

	public static int dehexchar(char c) {
		if (c >= '0' && c <= '9') {
			return c - '0';
		}
		if (c >= 'A' && c <= 'F') {
			return c - ('A' - 10);
		}
		if (c >= 'a' && c <= 'f') {
			return c - ('a' - 10);
		}
		return -1;
	}

	public boolean end() {
		return this.eof && !this.usePrevious;
	}

	public boolean more() {
		this.next();
		if (this.end()) {
			return false;
		}
		this.back();
		return true;
	}

	public char next(){
		int c;
		if (this.usePrevious) {
			this.usePrevious = false;
			c = this.previous;
		} else {
			try {
				c = this.reader.read();
			} catch (IOException exception) {
				throw new SmileRunException(exception);
			}

			if (c <= 0) { // End of stream
				this.eof = true;
				c = 0;
			}
		}
		this.index += 1;
		if (this.previous == '\r') {
			this.line += 1;
			this.character = c == '\n' ? 0 : 1;
		} else if (c == '\n') {
			this.line += 1;
			this.character = 0;
		} else {
			this.character += 1;
		}
		this.previous = (char) c;
		return this.previous;
	}

	public char next(char c)  {
		char n = this.next();
		if (n != c) {
			throw this.syntaxError("Expected '" + c + "' and instead saw '" + n
					+ "'");
		}
		return n;
	}

	public String next(int n){
		if (n == 0) {
			return "";
		}

		char[] chars = new char[n];
		int pos = 0;

		while (pos < n) {
			chars[pos] = this.next();
			if (this.end()) {
				throw this.syntaxError("Substring bounds error");
			}
			pos += 1;
		}
		return new String(chars);
	}

	public char nextClean()  {
		for (;;) {
			char c = this.next();
			if (c == 0 || c > ' ') {
				return c;
			}
		}
	}

	public String nextString(char quote) {
		char c;
		StringBuilder sb = new StringBuilder();
		forlabel:
		for (;;) {
			c = this.next();
			switch (c) {
				case 0:
				case '\n':
				case '\r':
					break forlabel;
				default:
					if (c == quote) {
						return sb.toString();
					}
					sb.append(c);
			}
		}
		return sb.toString();
	}

	public String nextTo(char delimiter) {
		StringBuffer sb = new StringBuffer();
		for (;;) {
			char c = this.next();
			if (c == delimiter || c == 0 || c == '\n' || c == '\r') {
				if (c != 0) {
					this.back();
				}
				return sb.toString().trim();
			}
			sb.append(c);
		}
	}

	public String nextTo(String delimiters){
		char c;
		StringBuffer sb = new StringBuffer();
		for (;;) {
			c = this.next();
			if (delimiters.indexOf(c) >= 0 || c == 0 || c == '\n' || c == '\r') {
				if (c != 0) {
					this.back();
				}
				return sb.toString().trim();
			}
			sb.append(c);
		}
	}

	public char skipTo(char to) throws JSONParseException {
		char c;
		try {
			int startIndex = this.index;
			int startCharacter = this.character;
			int startLine = this.line;
			this.reader.mark(Integer.MAX_VALUE);
			do {
				c = this.next();
				if (c == 0) {
					this.reader.reset();
					this.index = startIndex;
					this.character = startCharacter;
					this.line = startLine;
					return c;
				}
			} while (c != to);
		} catch (IOException exc) {
			throw new JSONParseException(exc);
		}

		this.back();
		return c;
	}

	public SmileRunException syntaxError(String message) {
		return new SmileRunException(message + this.toString());
	}

	public String toString() {
		return " at " + this.index + " [character " + this.character + " line "
				+ this.line + "]";
	}
}
